<script setup lang="ts">
  import { onMounted, ref, onBeforeUnmount } from "vue"
  import * as THREE from "three"
  import TWEEN from "three/examples/jsm/libs/tween.module.js"
  import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
  import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer"
  import { CSS3DRenderer, CSS3DObject } from "three/examples/jsm/renderers/CSS3DRenderer"
  import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer"
  import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass"
  import { pointPositions, lineIdxs, shapeIdxs, zdDatas, freq } from "@/assets/zdData-xt"

  onMounted(() => {
    width = document.querySelector(".contentBox")?.clientWidth || window.innerWidth
    height = (document.querySelector(".contentBox")?.clientHeight  - 60) || window.innerHeight
    initThree()
    shapeRennder()
  })
  onBeforeUnmount(() => {
    clearInterval(inter)
    scene?.traverse(item => {
      item?.material?.dispose()
      item?.geometry?.dispose()
    })
    WebGLRenderer?.dispose()
    composer?.dispose()
    camera = null
    scene = null
  })

  let scene: THREE.Scene
  let camera: THREE.PerspectiveCamera
  let width: number
  let height: number
  const initThree = () => {
    // 创建一个三维场景
    scene = new THREE.Scene()
    // 创建一个透视相机，窗口宽度，窗口高度
    camera = new THREE.PerspectiveCamera(
      // 视角 视角越大  能看到的范围就越大
      50,
      // 相机的宽高比  一般和画布一样大最好
      width / height,
      // 近平面  相机能看到最近的距离
      1,
      // 远平面  相机能看到最远的距离
      2000
    )
    camera.position.set(500, 150, 300)
    // 添加光源
    const ambient = new THREE.AmbientLight(0xffffff, 0.5)
    scene.add(ambient)
    // 创建辅助坐标轴
    // const axesHelper = new THREE.AxesHelper(1000)
    // scene.add(axesHelper)
    // 背景颜色
    scene.background = new THREE.Color("#000000")
    createRender()
    createEffect()
    createPoint()
    createLine()
    createShape()
    camera.lookAt(500, 0, 0)
    composer.render()
  }

  // 创建渲染器
  let composer: EffectComposer
  let WebGLRenderer: THREE.WebGLRenderer
  let CSS2Renderer: CSS2DRenderer
  let CSS3Renderer: CSS3DRenderer
  let controls: OrbitControls
  const myThreeRef = ref()
  const createRender = () => {
    // css2渲染标签
    CSS2Renderer = new CSS2DRenderer()
    CSS2Renderer.setSize( width, height )
    CSS2Renderer.domElement.style.position = 'absolute'
    CSS2Renderer.domElement.style.top = '0px'
    CSS2Renderer.domElement.style.pointerEvents = 'none'
    myThreeRef.value.appendChild(CSS2Renderer.domElement)

    // css3渲染标签
    CSS3Renderer = new CSS3DRenderer()
    CSS3Renderer.setSize( width, height )
    CSS3Renderer.domElement.style.position = 'absolute'
    CSS3Renderer.domElement.style.top = '0px'
    myThreeRef.value.appendChild(CSS3Renderer.domElement)

    // webgl渲染界面
    WebGLRenderer = new THREE.WebGLRenderer()
    // 设置渲染区尺寸
    WebGLRenderer.setSize(width, height)
    myThreeRef.value.appendChild(WebGLRenderer.domElement)

    // 相机控件
    controls = new OrbitControls(camera, CSS3Renderer.domElement)
    controls.target.set(500, 0, 0)
    controls.addEventListener("change", () => {
      // 相机变化的时候渲染
      composer.render()
      CSS2Renderer.render(scene, camera)
      CSS3Renderer.render(scene, camera)
    })

    // 缩放控制
    window.onresize = function () {
      width = document.querySelector(".contentBox")?.clientWidth || window.innerWidth
      height = (document.querySelector(".contentBox")?.clientHeight  - 60) || window.innerHeight
      WebGLRenderer.setSize(width, height)
      CSS2Renderer.setSize(width, height)
      CSS3Renderer.setSize(width, height)
      camera.aspect = width / height
      camera.updateProjectionMatrix()
      composer.render()
      CSS2Renderer.render(scene, camera)
      CSS3Renderer.render(scene, camera)
    }
  }

  // 效果合成器 使用效果器后WebGLRenderer不需要用了 直接调用效果器渲染
  const createEffect = () => {
    // 创建一个EffectComposer（效果组合器）对象，然后在该对象上添加后期处理通道。
    composer = new EffectComposer(WebGLRenderer)
    // 新建一个场景通道  为了覆盖到原来的场景上
    const renderPass = new RenderPass(scene, camera)
    composer.addPass(renderPass)
  }

  const pointsList: typeof pointPositions = pointPositions
  let cube: THREE.Mesh
  let cubeList: any[] = []
  let cubeListPos: any[] = []
  let cubeGroup: any
  // 创建点位
  const createPoint = () => {
    cubeGroup = new THREE.Group()
    cubeGroup.name = "cubeList"
    pointsList.forEach(pos => {
      const cubeGeometry = new THREE.BoxGeometry(0.3, 0.3, 0.3)
      const cubeMaterial = new THREE.MeshBasicMaterial({
        color: 0xffffff,
        transparent: true,
        opacity: 0.6
      })
      cube = new THREE.Mesh(cubeGeometry, cubeMaterial)
      cube.name = pos.idx.toString()
      cube.position.set(pos.x, pos.y, pos.z)
      cubeGroup.add(cube)
      cubeList.push(cube)
      cubeListPos.push(cube.position)
    })
    scene.add(cubeGroup)
  }

  // 创建线
  let lineMesh: any
  const createLine = () => {
    const points: any[] = []
    for(let i=0; i<lineIdxs.length; i++) {
      points.push( cubeListPos[lineIdxs[i][0]], cubeListPos[lineIdxs[i][1]] )
    }
    const lineMaterial = new THREE.LineBasicMaterial({
      color: 0x2cf63d,
      linewidth: 3,
    })
    const geometry = new THREE.BufferGeometry().setFromPoints( points )
    lineMesh = new THREE.LineSegments( geometry, lineMaterial )
    lineMesh.name = "lineMesh"
    scene.add(lineMesh)
  }

  //创建面
  let shapeMesh: THREE.Mesh
  const createShape = () => {
    // 三角面
    const arr32: number[] = []
    cubeListPos.forEach(i => {
      arr32.push(i.x, i.y, i.z)
    })
    // 索引
    // const index = _generateIdx2(cubeListPos)
    const index = _generateIdx3(shapeIdxs)

    // 颜色的索引和颜色
    const colors = _generateColorIdx(cubeListPos)
    _generateColor(colors)

    // 新的时间点创建新的位置
    const shapeGeometry = new THREE.BufferGeometry()
    const shapeMaterial = new THREE.MeshBasicMaterial({
      vertexColors: true,
      side: THREE.DoubleSide
    })
    // 将新的位置 index 颜色加入进去生成一个面
    shapeGeometry.setAttribute('position', new THREE.BufferAttribute( new Float32Array(arr32), 3 ))
    shapeGeometry.setIndex(index)
    shapeGeometry.setAttribute("color", new THREE.BufferAttribute( new Float32Array(colors), 3 ))
    shapeMesh = new THREE.Mesh(shapeGeometry, shapeMaterial)
    shapeMesh.name = "shapeMesh"
    scene.add(shapeMesh)
  }

  let frameStep = 0
  // 竖向
  const speedY = function(dz: number, frameStep: number): number {
    return zfLevel.value * dz * Math.cos(frameStep * (360 / freq))
  }
  // 横向
  const speedX = function(dz: number, frameStep: number): number {
    return zfLevel.value * dz * Math.cos(frameStep * (360 / freq))
  }
  const speedZ = function(dz: number, frameStep: number): number {
    return zfLevel.value * dz * Math.cos(frameStep * (360 / freq))
  }

  // 动画移动
  const shapeAnimation = (zdList: any[], frameStep: number) => {
    // 移除老的位置
    scene.remove(shapeMesh)
    scene.remove(lineMesh)
    // 循环每个点位 用原本的位置+位移的位置成为新的动画移动目标数值
    for(let i = 0; i < cubeList.length; i++) {
      const tween = new TWEEN.Tween(cubeList[i].position)
      tween.to({
        y: pointsList[i].y + speedY(zdList[i].y, frameStep),
        x: pointsList[i].x + speedX(zdList[i].x, frameStep),
        z: pointsList[i].z + speedZ(zdList[i].z, frameStep)
      }, 1000)
        .easing(TWEEN.Easing.Back.Out)
        .start()
    }
    createShape()
    createLine()
  }

  setInterval(() => {
    frameStep = frameStep + 1
  }, 1000)
  // 渲染 setInterval方法使用动画
  let inter
  const shapeRennder = () => {
    const zdList = zdDatas
    inter = setInterval(() => {
      TWEEN.update()
      shapeAnimation(zdList, frameStep)
      composer.render()
      CSS2Renderer.render(scene as THREE.Scene, camera as THREE.PerspectiveCamera)
    }, 100)
  }
  // requestAnimationFrame方法使用动画
  const shapeRennder2 = () => {
    const zdList = zdDatas
    TWEEN.update()
    shapeAnimation(zdList, frameStep)
    composer.render()
    CSS2Renderer.render(scene, camera)
    requestAnimationFrame(shapeRennder2)
  }
  // 生成索引 只能针对顺序index
  // 0-2-4
  // 1-3-5
  const _generateIdx = (pointList) => {
    const list: any[] = []
    for(let i=0;i<pointList.length;i++) {
      if (pointList.slice(i,i+3).length >= 3) {
        list.push(pointList.slice(i,i+3))
      }
    }
    const list2: number[] = []
    list.forEach(i => {
      i.forEach(j => {
        list2.push(j.idx)
      })
    })
    return list2
  }
  // 0-1-2
  // 3-4-5
  const _generateIdx2 = (pointList) => {
    const list: number[] = []
    const len = pointList.length
    for(let i=0;i<len-2;i++) {
      if (i === 0) {
        list.push(i, i+1, len/2)
      } else if (i === 1) {
        list.push(i, len/2, len/2+1)
      } else if (i > 1 && i % 2 == 1) {
        list.push(i-(i/2-0.5), len/2+(i/2-0.5), len/2+(i/2+0.5))
      } else if (i > 1 && i % 2 == 0) {
        list.push(i-(i/2), i-(i/2-1), len/2+(i/2))
      }
    }
    return list
  }
  // 0-1
  // 2-3
  const _generateIdx3 = (shapeIdxs) => {
    const list: number[] = []
    for(let i=0;i<shapeIdxs.length;i++) {
      list.push(shapeIdxs[i][0], shapeIdxs[i][1], shapeIdxs[i][2])
      list.push(shapeIdxs[i][2], shapeIdxs[i][3], shapeIdxs[i][0])
    }
    return list
  }

  // 生成颜色
  const _generateColor = (colors) => {
    const arr: number[] = []
    cubeListPos.forEach(i => {
      arr.push(i.y)
    })
    const max = Math.max(...arr)
    const min = Math.min(...arr)
    const height = max - min
    const c1 = new THREE.Color(0x00ff00)
    const c2 = new THREE.Color(0xff0000)
    for(let i = 0; i < cubeListPos.length; i++) {
      // const percent = (Math.abs(cubeListPos[i].y) - min - 0.4) / height
      // const c = c1.clone().lerp(c2, percent)
      // colors[i * 3] = c.r
      // colors[i * 3 + 1] = c.g
      // colors[i * 3 + 2] = c.b

      colors[i * 3] = Math.abs(cubeListPos[i].y)
      colors[i * 3 + 1] = 1 - Math.abs(cubeListPos[i].y) * 0.25
      colors[i * 3 + 2] = 0

      // colors[i * 3] = _normalizeValue(cubeListPos[i].y, max, min)
      // colors[i * 3 + 1] = 1 - _normalizeValue(cubeListPos[i].y, max, min) * 0.2
      // colors[i * 3 + 2] = 0

    }
  }

  // 生成颜色的index
  const _generateColorIdx = (pointList) => {
    const list: number[] = []
    const len = pointList.length
    for(let i = 0; i < len; i++) {
      // 红
      list[i * 3] = 0
      // 绿
      list[i * 3 + 1] = 1
      // 蓝
      list[i * 3 + 2] = 0
    }
    return list
  }

  // 创建css2标签
  let txtTag:any
  const createCSS2Tag = (object: any, name: any = "测试名字css2") => {
    const txtDiv = document.createElement( 'div' )
    txtDiv.innerHTML = `<div class="testTag">
                          <div>标签</div>
                          <div>${name}</div>
                        </div>`
    txtDiv.style.backgroundColor = '#ffffff'
    txtTag = new CSS2DObject(txtDiv)
    txtTag.position.set(0, 0, 0)
    txtTag.layers.set(0)
    txtTag.name = "css2tag"
    object.add(txtTag)
  }

  // 增幅
  const zfList = ref([
    {
      value: 0,
      label: "无增幅"
    },
    {
      value: 100,
      label: "增幅1"
    },
    {
      value: 200,
      label: "增幅2"
    }
  ])
  let zfLevel = ref(20000)

  // 标签显隐
  const changeTag = () => {
    if (txtTag.parent) {
      txtTag.parent.remove(txtTag)
    } else {
      createCSS2Tag(scene.getObjectByName("100"), "新的")
    }
  }

</script>

<template>
  <div class="contentBox">
    <div class="contentBox-button">
      <!--<el-select-->
      <!--  style="width: 160px"-->
      <!--  v-model="zfLevel"-->
      <!--  placeholder="请选择"-->
      <!--&gt;-->
      <!--  <el-option-->
      <!--    v-for="item in zfList"-->
      <!--    :key="item.value"-->
      <!--    :label="item.label"-->
      <!--    :value="item.value"-->
      <!--  />-->
      <!--</el-select>-->
      增幅：<el-input v-model="zfLevel" style="width: 160px" type="number" />
      <!--<el-button type="primary" @click="changeTag">确认增幅</el-button>-->
    </div>
    <div class="threeBox">
      <div class="myThree2" ref="myThreeRef"></div>
    </div>
  </div>
</template>

<style scoped lang="scss">
  .contentBox {
    width: 100%;
    height: 100%;
    overflow: hidden;
    .threeBox {
      margin-top: 20px;
    }
    .myThree2 {
      width: 100%;
      height: 100%;
      position: relative;
    }
  }
</style>